import * as vscode from 'vscode';

/**
 * SML代码格式化器
 * 内部实现的格式化器替代外部smlformat工具
 */
export class SmlFormatter implements vscode.DocumentFormattingEditProvider, vscode.DocumentRangeFormattingEditProvider {

    /**
     * 获取用户配置的格式化选项
     */
    private getFormattingOptions(): { enable: boolean, indentSize: number, useSpaces: boolean, useExternalTool: boolean } {
        const config = vscode.workspace.getConfiguration('sml.format');
        return {
            enable: config.get('enable', true),
            indentSize: config.get('indentSize', 2),
            useSpaces: config.get('useSpaces', true),
            useExternalTool: config.get('useExternalTool', true)
        };
    }

    /**
     * 格式化整个文档
     */
    public async provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
        const formatConfig = this.getFormattingOptions();
        if (!formatConfig.enable) {
            return [];
        }

        try {
            const formattedText = await this.formatInternal(document.getText(), formatConfig);
            const start = new vscode.Position(0, 0);
            const end = new vscode.Position(document.lineCount - 1, document.lineAt(document.lineCount - 1).text.length);
            const range = new vscode.Range(start, end);
            return [vscode.TextEdit.replace(range, formattedText)];
        } catch (error) {
            vscode.window.showErrorMessage(`SML格式化失败: ${error}`);
            return [];
        }
    }

    /**
     * 格式化文档的指定范围
     */
    public async provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
        const formatConfig = this.getFormattingOptions();
        if (!formatConfig.enable) {
            return [];
        }

        try {
            const textToFormat = document.getText(range);
            const formattedText = await this.formatInternal(textToFormat, formatConfig);
            return [vscode.TextEdit.replace(range, formattedText)];
        } catch (error) {
            vscode.window.showErrorMessage(`SML格式化失败: ${error}`);
            return [];
        }
    }

    /**
     * 内部格式化实现 - 改进版本，更好地支持SML缩进和模式匹配
     */
    private async formatInternal(text: string, config: { indentSize: number, useSpaces: boolean, useExternalTool: boolean }): Promise<string> {
        // 如果启用外部工具，优先尝试调用 smlfmt
        if (config.useExternalTool) {
            try {
                const formattedText = await this.callSmlfmt(text);
                return formattedText;
            } catch (error) {
                // 如果外部工具失败，回退到内部实现
                vscode.window.showWarningMessage('外部 smlfmt 工具调用失败，使用内部格式化器');
            }
        }
        // 回退到内部实现
        return this.internalFormat(text, config);
    }

    /**
     * 调用外部 smlfmt 工具
     */
    private async callSmlfmt(text: string): Promise<string> {
        return new Promise((resolve, reject) => {
            const tmp = require('tmp');
            const fs = require('fs');
            const { exec } = require('child_process');

            tmp.file({ postfix: '.sml' }, (err: Error, tmpPath: string, fd: number, cleanupCallback: () => void) => {
                if (err) return reject(err);

                fs.writeFile(tmpPath, text, (err: Error) => {
                    if (err) {
                        cleanupCallback();
                        return reject(err);
                    }

                    exec(`smlfmt --force ${tmpPath}`, (error: Error, stdout: string, stderr: string) => {
                        cleanupCallback();
                        if (error) return reject(stderr || error);

                        fs.readFile(tmpPath, 'utf8', (err: Error, formattedText: string) => {
                            if (err) return reject(err);
                            resolve(formattedText);
                        });
                    });
                });
            });
        });
    }

    /**
     * 内部格式化实现
     */
    private internalFormat(text: string, config: { indentSize: number, useSpaces: boolean }): string {
        const lines = text.split('\n');
        const formattedLines: string[] = [];
        let indentLevel = 0;
        const indent = config.useSpaces ? ' '.repeat(config.indentSize) : '\t';

        // Opening keywords that increase indentation
        const openingKeywords = ['let', 'local', 'struct', 'sig', 'case'];

        // Closing keywords that decrease indentation
        const closingKeywords = ['in', 'end'];

        // Continuation keywords that align with parent
        const continuationKeywords = ['|'];

        // Track if we're in a function pattern matching context
        let inFunctionPattern = false;
        let functionIndent = ''; // Stores the indent level of the initial fun declaration
        let inIfThenElse = false; // Track if we're in an if-then-else construct

        for (let i = 0; i < lines.length; i++) {
            const line = lines[i];
            const trimmedLine = line.trim();

            // Skip empty lines
            if (trimmedLine === '') {
                formattedLines.push('');
                continue;
            }

            // Extract first word for keyword checking
            const firstWord = trimmedLine.split(/\s+/)[0].replace(/[^\w|]/g, '');

            // Handle decreasing indentation keywords
            if (closingKeywords.includes(firstWord)) {
                indentLevel = Math.max(0, indentLevel - 1);
                // Reset function pattern context when we hit 'end'
                if (firstWord === 'end') {
                    inFunctionPattern = false;
                    functionIndent = '';
                }
                // Reset if-then-else context
                if (inIfThenElse && (firstWord === 'in' || firstWord === 'end')) {
                    inIfThenElse = false;
                }
            }

            // Handle continuation keywords (align with parent)
            if (continuationKeywords.includes(firstWord)) {
                // Special handling for function pattern matching
                if (inFunctionPattern) {
                    // Align with the function name position
                    formattedLines.push(functionIndent + '  ' + trimmedLine);
                } else {
                    // For case expressions, align with case
                    const adjustedIndent = indent.repeat(Math.max(0, indentLevel - 1));
                    formattedLines.push(adjustedIndent + trimmedLine);
                }
            } else {
                // Apply normal indentation
                const currentIndent = indent.repeat(indentLevel);
                formattedLines.push(currentIndent + trimmedLine);

                // Check if this line starts a function definition
                if (firstWord === 'fun') {
                    inFunctionPattern = true;
                    functionIndent = currentIndent;
                }

                // Check if this line starts an if construct
                if (trimmedLine.includes('if ') && trimmedLine.includes(' then')) {
                    inIfThenElse = true;
                }
            }

            // Handle increasing indentation keywords
            // Check if line ends with an opening keyword
            const words = trimmedLine.split(/\s+/);
            const lastWord = words[words.length - 1]?.replace(/[^\w]/g, '') || '';

            if (openingKeywords.includes(lastWord)) {
                indentLevel++;
            }

            // Special handling for if-then-else constructs
            if (inIfThenElse) {
                if (firstWord === 'then' || firstWord === 'else') {
                    // These should be indented one level more than the if statement
                    indentLevel++;
                }
            } else if (trimmedLine.includes('if ') && trimmedLine.includes(' then')) {
                // Start of if-then-else construct
                indentLevel++;
            }
        }

        return formattedLines.join('\n');
    }
}